﻿using BarManager.Core.ApplicationService.Interfaces;
using BarManager.Core.BusinessRules.Interfaces.Validator;
using BarManager.Core.DomainModel;
using BarManager.Core.DomainService;
using BarManager.Core.Entity;
using System;
using System.Collections.Generic;
using System.Linq;

namespace BarManager.Core.ApplicationService
{
    public class OrdenService : BaseApplicationService<Orden>, IOrdenService
    {
        private IOrdenRepository Repository
        {
            get { return (IOrdenRepository)this.BaseRepository; }
        }
        private IOrdenDetalleRepository OrdenDetalleRepository { get; set; }
        private IProductoService ProductoService { get; set; }
        private IMesaService MesaService { get; set; }

        public OrdenService(IObjectContext context, IOrdenRepository repository, IProductoService productoService,
            IMesaService mesaService, IOrdenDetalleRepository ordenDetalleRepository, IOrdenValidator validator)
            : base(context, repository, validator)
        {
            this.ProductoService = productoService;
            this.MesaService = mesaService;
            this.OrdenDetalleRepository = ordenDetalleRepository;
        }

        public override IEnumerable<string> Add(Orden entity)
        {
            if (Validator.IsValid(entity, ValidatorType.Add))
            {
                using (var tx = UnitOfWork.StartTransaction())
                {
                    //- Marcar Mesa en uso
                    MesaService.CambiarDisponibilidad(entity.MesaId, false);

                    //- Descontar productos de stock
                    ProductoService.Stock(entity.Detalle, MovimientoStock.Descontar);

                    //- Guardar Orden
                    Repository.Add(entity);

                    //- Commit
                    UnitOfWork.Commit();
                    tx.Complete();
                }
            }
            return Validator.BrokenRules();
        }

        public override IEnumerable<string> Update(Orden entity)
        {
            if (Validator.IsValid(entity, ValidatorType.Update))
            {
                using (var tx = UnitOfWork.StartTransaction())
                {
                    #region Actualizamos cabecera
                    Orden orden = Repository.Get(x => x.Id == entity.Id);
                    orden.EmpleadoId = entity.EmpleadoId;                    
                    #endregion

                    #region Actualizamos mesa
                    //- Si cambio la mesa validamos que la nueva este libre
                    if (orden.MesaId != entity.MesaId)
                    {
                        //- Liberamos mesa vieja
                        MesaService.CambiarDisponibilidad(orden.MesaId, true);

                        //- Actualizamos la mesa de la orden
                        orden.MesaId = entity.MesaId;
                       
                        //- Ocupamos nueva mesa
                        MesaService.CambiarDisponibilidad(entity.MesaId, false);
                    }
                    #endregion

                    #region Actualizamos detalle
                    //- Buscamos detalle viejos en BD
                    var list = OrdenDetalleRepository.Find(x => x.OrdenId == entity.Id);

                    //- Agregamos o actualizamos items
                    foreach (var item in entity.Detalle)
                    {
                        //- Buscamos si existe el item en BD
                        var aux = list.FirstOrDefault(x => x.Id == item.Id);

                        //- Si existe y no son iguales actualiza
                        if (aux != null && !EqualsItems(aux, item)) UpdateDetalle(aux, item);

                        //- Si es null es nuevo item
                        if (aux == null)
                        {
                            //- Seteamos a que orden pertenece el nuevo item
                            item.OrdenId = entity.Id;
                            AddDetalle(item);
                        }
                    }

                    //- Eliminamos items que fueron quitados del detalle
                    foreach (var item in list)
                    {
                        //- Buscamos si existe el item en memoria
                        var aux = entity.Detalle.FirstOrDefault(x => x.Id == item.Id);

                        //- Si es null es un item a eliminar
                        if (aux == null) RemoveDetalle(item);
                    }
                    #endregion

                    //- Commit
                    UnitOfWork.Commit();
                    tx.Complete();
                }
            }
            return Validator.BrokenRules();

        }

        public void Invoice(long id)
        {
            using (var tx = UnitOfWork.StartTransaction())
            {
                //- Obtenemos la Orden a facturar y facturamos
                Orden orden = Repository.Get(x => x.Id == id);
                orden.Estado = EstadoOrden.Facturada;
                orden.FechaCierre = DateTime.Now;

                //- Seteamos estado detalle como pagado, todo lo q no sea regalo
                orden.Detalle.All(x =>
                {
                    x.CantidadPagada = (x.Cantidad - x.CantidadRegalada);
                    x.Estado = EstadoDetalle.Entregado;
                    return true;
                });

                //- Liberamos mesa
                MesaService.CambiarDisponibilidad(orden.Mesa.Id, true);

                //- Commit
                UnitOfWork.Commit();
                tx.Complete();
            }
        }

        public void Cancel(long id)
        {
            using (var tx = UnitOfWork.StartTransaction())
            {
                //- Obtenemos la Orden a cancelar y cancelamos
                Orden orden = Repository.Get(x => x.Id == id);
                orden.Estado = EstadoOrden.Cancelada;
                orden.FechaCierre = DateTime.Now;

                //- Cancelamos estado items
                orden.Detalle.All(x =>
                {
                    x.CantidadPagada = 0;
                    x.CantidadRegalada = 0;
                    x.Estado = EstadoDetalle.Cancelado;
                    return true;
                });

                //- Reponemos Stock               
                ProductoService.Stock(orden.Detalle, MovimientoStock.Agregar);

                //- Liberamos mesa
                MesaService.CambiarDisponibilidad(orden.Mesa.Id, true);

                //- Commit
                UnitOfWork.Commit();
                tx.Complete();
            }

        }

        #region Metodos Auxiliares
        private void AddDetalle(OrdenDetalleBase item)
        {
            ProductoService.Stock(item, item.Cantidad, MovimientoStock.Descontar);
            
            //- Si es producto quita relaciones
            if (item is OrdenDetalleProducto) 
                (item as OrdenDetalleProducto).Producto = null;

            //- Si es promocion quita relaciones de productos
            if (item is OrdenDetallePromocion)
                (item as OrdenDetallePromocion).Promocion = null;
            OrdenDetalleRepository.Add(item);
        }

        private void UpdateDetalle(OrdenDetalleBase itemOld, OrdenDetalleBase itemNew)
        {
            if (itemOld.Cantidad > itemNew.Cantidad)
                ProductoService.Stock(itemOld, itemOld.Cantidad - itemNew.Cantidad, MovimientoStock.Agregar);
            else if (itemOld.Cantidad < itemNew.Cantidad)
                ProductoService.Stock(itemOld, itemNew.Cantidad - itemOld.Cantidad, MovimientoStock.Descontar);

            itemOld.Cantidad = itemNew.Cantidad;
            itemOld.CantidadPagada = itemNew.CantidadPagada;
            itemOld.CantidadRegalada = itemNew.CantidadRegalada;  
        }

        private void RemoveDetalle(OrdenDetalleBase item)
        {
            ProductoService.Stock(item, item.Cantidad, MovimientoStock.Agregar);
            OrdenDetalleRepository.Delete(item);
        }

        private bool EqualsItems(OrdenDetalleBase itemA, OrdenDetalleBase itemB)
        {
            return (itemA.Cantidad == itemB.Cantidad &&
                    itemA.CantidadPagada == itemB.CantidadPagada &&
                    itemA.CantidadRegalada == itemB.CantidadRegalada);
        } 
        #endregion
    }
}
